package com.cn.stepLock;

import com.cn.stepLock.chain.*;
import com.cn.stepLock.event.BaseEvent;
import com.cn.stepLock.event.StartExecuteTaskEvent;
import com.cn.stepLock.exception.MessageException;
import com.cn.stepLock.executeLambda.BaseLambda;
import com.cn.stepLock.executeLambda.BiConsumerLambda;
import com.google.common.collect.ImmutableList;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;

import static com.cn.stepLock.lambda.LambdaUtil.*;

/**
 * @author Aaron
 * @desc
 * @createTime 2022-02-21 20:34
 * @since
 */
public class ObjectStepLock {

    protected ChainHelper chainHelper;

    protected AtomicBoolean initExecuteChain=new AtomicBoolean(false);

    private final int maxExecuteThreadNum=1<<4;

    public void execute(ExecuteRequest executeRequest){
        parameterCheck(executeRequest);
        List<Task> taskList = initTaskList(executeRequest);
        initExecuteChain(taskList);
        executeTask(taskList);
    }

    public void stop(){
        chainHelper.stop();
    }

    private void executeTask(List<Task> initTaskCollection){
        BaseEvent startExecuteTaskEvent = new StartExecuteTaskEvent(initTaskCollection.get(0));
        ChainEventApplication.pulishEvent(startExecuteTaskEvent);
    }

    private void initExecuteChain(List<Task> taskList){
        /**
         * 这个while 循环是为了防止两个线程同时竞争initExecuteChain参数的时候
         * 竞争失败的那个线程在竞争成功的线程真正实例化currentExecuteChain之前去执行mergeTaskAndExecuteChain方法
         * 相当于CAS自旋
         */
        while (true){
            if (initExecuteChain.compareAndSet(false, true)){
                this.chainHelper = new ChainHelper().initExecuteChain(taskList);
            }
            if (chainHelper!=null){
                ifThrow(taskList.size()!= chainHelper.getProcessChainCount(),()->new MessageException(" the method count can not match the inited chain count"));
                break;
            }
        }
    }

    private void parameterCheck(ExecuteRequest executeRequest) {
        Objects.requireNonNull(executeRequest);
        ifThrow(executeRequest.getExecuteLambdaList().isEmpty() || executeRequest.getExecuteLambdaList().size()==1,()->new MessageException("the method you intend to invoke must not be empty or single element!"));
    }

    private List<Task> initTaskList(ExecuteRequest executeRequest){
        Thread thread = Thread.currentThread();
        return bindTaskChain(transferList(executeRequest.getExecuteLambdaList(), x -> new Task(x, thread)));
    }

    private List<Task> bindTaskChain(List<Task> taskList){
        Stream.iterate(0,n->n+1).limit(taskList.size()-1).forEach(n-> ChainUtils.addTask(taskList.get(n),taskList.get(n+1)));
        return taskList;
    }

}
